using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Windows.Forms;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;

namespace Microsoft.Samples.SqlServer.Dts
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")]
    class RemoveDuplicatesUI : DataFlowComponentUI
    {
        const string IsDistinctOutputPropName = "IsDistinctOutput";
        const string InputColumnLineageIdPropName = "InputColumnLineageId";

        /// <summary>
        /// Communication with UI form goes through these events. The UI will raise events when some data/action
        /// is needed and this class is responsible to answer those requests. This way we create separation between UI 
        /// specific logic and interactions with SSIS data flow object model.
        /// </summary>
        /// <param name="form"></param>
        private void HookupEvents(RemoveDuplicatesForm form)
        {
            form.GetAvailableColumns += new GetAvailableColumnsEventHandler(form_GetAvailableColumns);
            form.GetSelectedInputOutputColumns += new GetSelectedInputOutputColumnsEventHandler(form_GetSelectedInputOutputColumns);
            form.SetInputOutputColumns += new ChangeInputOutputColumnsEventHandler(form_SetInputOutputColumns);
            form.DeleteInputOutputColumns += new ChangeInputOutputColumnsEventHandler(form_DeleteInputOutputColumns);
            form.ChangeOutputColumnName += new ChangeOutputColumnNameEventHandler(form_ChangeOutputColumnName);
        }

        #region Virtual methods

        /// <summary>
        /// Implementation of the method resposible for displaying the form.
        /// This one is abstract in the base class.
        /// </summary>
        /// <param name="parentControl"></param>
        /// <returns></returns>
        protected override bool EditImpl(IWin32Window parentControl)
        {
            using (RemoveDuplicatesForm form = new RemoveDuplicatesForm())
            {
                this.HookupEvents(form);

                if (form.ShowDialog(parentControl) == DialogResult.OK)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }

        #endregion

        # region Event handlers

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        void form_GetAvailableColumns(object sender, AvailableColumnsArgs args)
        {
            Debug.Assert(this.VirtualInput != null, "Virtual input is not valid.");

            this.ClearErrors();

            try
            {
                IDTSVirtualInputColumnCollection100 virtualInputColumnCollection = this.VirtualInput.VirtualInputColumnCollection;
                int virtualInputColumnsCount = virtualInputColumnCollection.Count;

                args.AvailableColumns = new AvailableColumnElement[virtualInputColumnsCount];
                for (int i = 0; i < virtualInputColumnsCount; i++)
                {
                    IDTSVirtualInputColumn100 virtualInputColumn = virtualInputColumnCollection[i];
                    args.AvailableColumns[i].Selected = virtualInputColumn.UsageType != DTSUsageType.UT_IGNORED;
                    args.AvailableColumns[i].AvailableColumn = new DataFlowElement(virtualInputColumn.Name, virtualInputColumn);
                }
            }
            catch (Exception ex)
            {
                this.ReportErrors(ex);
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        void form_GetSelectedInputOutputColumns(object sender, SelectedInputOutputColumnsArgs args)
        {
            this.ClearErrors();
            try
            {
                IDTSInput100 input = this.ComponentMetadata.InputCollection[0];

                IDTSOutput100 distinctOutput = this.GetDistinctOrDuplicateOutput(true);
                IDTSOutput100 duplicateOutput = this.GetDistinctOrDuplicateOutput(false);

                Dictionary<int, IDTSOutputColumn100> distinctColumnsByInputColumnID = GetOutputColumnsDictionary(distinctOutput);
                Dictionary<int, IDTSOutputColumn100> duplicateColumnsByInputColumnID = GetOutputColumnsDictionary(duplicateOutput);

                IDTSInputColumnCollection100 inputColumnCollection = input.InputColumnCollection;
                int inputColumnCount = inputColumnCollection.Count;

                args.SelectedColumns = new SelectedInputOutputColumns[inputColumnCount];

                for (int i = 0; i < inputColumnCount; i++)
                {
                    IDTSInputColumn100 inputColumn = inputColumnCollection[i];
                    int inputColumnLineageID = inputColumn.LineageID;

                    args.SelectedColumns[i].InputColumn = new DataFlowElement(inputColumn.Name, inputColumn);
                    args.SelectedColumns[i].DistinctColumn = GetMappedOutputColumnElement(inputColumnLineageID, distinctColumnsByInputColumnID);
                    args.SelectedColumns[i].DuplicateColumn = GetMappedOutputColumnElement(inputColumnLineageID, duplicateColumnsByInputColumnID);
                }
            }
            catch (Exception ex)
            {
                this.ReportErrors(ex);
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        void form_SetInputOutputColumns(object sender, SetInputOutputColumnsArgs args)
        {
            Debug.Assert(args.VirtualColumn != null, "Invalid arguments passed from the UI");

            this.ClearErrors();
            try
            {
                IDTSInput100 input = this.ComponentMetadata.InputCollection[0];

                IDTSVirtualInputColumn100 virtualInputColumn = args.VirtualColumn.Tag as IDTSVirtualInputColumn100;
                if (virtualInputColumn == null)
                {
                    throw new ApplicationException(Properties.Resources.UIisInconsistentState);
                }

                int lineageId = virtualInputColumn.LineageID;

                IDTSOutput100 distinctOutput = this.GetDistinctOrDuplicateOutput(true);
                IDTSOutput100 duplicateOutput = this.GetDistinctOrDuplicateOutput(false);

                IDTSInputColumn100 inputColumn = this.DesigntimeComponent.SetUsageType(input.ID, this.VirtualInput, lineageId, DTSUsageType.UT_READONLY);

                args.GeneratedColumns.InputColumn = new DataFlowElement(inputColumn.Name, inputColumn);
                args.GeneratedColumns.DistinctColumn = FindMappedOutputColumnElement(lineageId, distinctOutput);
                args.GeneratedColumns.DuplicateColumn = FindMappedOutputColumnElement(lineageId, duplicateOutput);
            }
            catch (Exception ex)
            {
                this.ReportErrors(ex);
                args.CancelAction = true;
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        void form_DeleteInputOutputColumns(object sender, SetInputOutputColumnsArgs args)
        {
            Debug.Assert(args.VirtualColumn != null, "Invalid arguments passed from the UI");

            this.ClearErrors();
            try
            {
                IDTSInput100 input = this.ComponentMetadata.InputCollection[0];

                IDTSVirtualInputColumn100 virtualInputColumn = args.VirtualColumn.Tag as IDTSVirtualInputColumn100;
                if (virtualInputColumn == null)
                {
                    throw new ApplicationException(Properties.Resources.UIisInconsistentState);
                }

                int lineageId = virtualInputColumn.LineageID;

                this.DesigntimeComponent.SetUsageType(input.ID, this.VirtualInput, lineageId, DTSUsageType.UT_IGNORED);
            }
            catch (Exception ex)
            {
                this.ReportErrors(ex);
                args.CancelAction = true;
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        void form_ChangeOutputColumnName(object sender, ChangeOutputColumnNameArgs args)
        {
            Debug.Assert(args.OutputColumn != null, "Invalid arguments passed from the UI");

            this.ClearErrors();
            try
            {
                IDTSOutputColumn100 outputColumn = args.OutputColumn.Tag as IDTSOutputColumn100;
                if (outputColumn == null)
                {
                    throw new ApplicationException(Properties.Resources.UIisInconsistentState);
                }

                outputColumn.Name = args.OutputColumn.Name;
            }
            catch (Exception ex)
            {
                this.ReportErrors(ex);
                //args.CancelAction = true;
            }
        }

        #endregion

        #region Helper methods

        /// <summary>
        /// Gets the outputs that contain distinct or duplicate rows based on the passed in flag.
        /// </summary>
        /// <param name="isDistinctFlag">true - return distinct rows output, false - return duplicate rows output</param>
        /// <returns>Appropriate output</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")]
        private IDTSOutput100 GetDistinctOrDuplicateOutput(bool isDistinctFlag)
        {
            IDTSOutput100 returnOutput = null;
            IDTSOutputCollection100 outputCollection = this.ComponentMetadata.OutputCollection;

            foreach (IDTSOutput100 output in outputCollection)
            {
                IDTSCustomProperty100 property = GetCustomProperty(IsDistinctOutputPropName, output.CustomPropertyCollection);
                if (property != null && property.Value is bool)
                {
                    bool isDistinct = (bool)property.Value;
                    if (isDistinct == isDistinctFlag)
                    {
                        returnOutput = output;
                        break;
                    }
                }
            }

            if (returnOutput != null)
            {
                return returnOutput;
            }
            else
            {
                throw new ApplicationException(Properties.Resources.CouldNotFindRequiredOutput);
            }
        }

        /// <summary>
        /// Gets all output columns from a given output indexed by lineage IDs of
        /// mapped inputs columns.
        /// </summary>
        /// <param name="output">passed output object</param>
        /// <returns>generated hash table</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")]
        private static Dictionary<int, IDTSOutputColumn100> GetOutputColumnsDictionary(IDTSOutput100 output)
        {
            Debug.Assert(output != null);

            IDTSOutputColumnCollection100 outputColumnCollection = output.OutputColumnCollection;
            int outputColumnCount = outputColumnCollection.Count;

            Dictionary<int, IDTSOutputColumn100> outputColumnByLineageID = new Dictionary<int, IDTSOutputColumn100>(outputColumnCount);

            foreach (IDTSOutputColumn100 outputColumn in outputColumnCollection)
            {
                IDTSCustomProperty100 property = GetCustomProperty(InputColumnLineageIdPropName, outputColumn.CustomPropertyCollection);
                if (property != null && property.Value is int)
                {
                    int lineageId = (int)property.Value;
                    if (outputColumnByLineageID.ContainsKey(lineageId))
                    {
                        // This should not happen. There should not be more than one output column,
                        // in the same collection, pointing to the same input column.
                        throw new ApplicationException("Duplicate output column mapping found.");
                    }
                    else
                    {
                        outputColumnByLineageID.Add(lineageId, outputColumn);
                    }
                }
            }

            return outputColumnByLineageID;
        }

        /// <summary>
        /// Get output column from the given dictionary mapped to the given lineage ID.
        /// </summary>
        /// <param name="lineageID"></param>
        /// <param name="outputColumnsByLineageID"></param>
        /// <returns></returns>
        private static DataFlowElement GetMappedOutputColumnElement(int lineageID, Dictionary<int, IDTSOutputColumn100> outputColumnsByLineageID)
        {
            Debug.Assert(outputColumnsByLineageID != null);

            if (outputColumnsByLineageID.ContainsKey(lineageID))
            {
                IDTSOutputColumn100 outputColumn = outputColumnsByLineageID[lineageID];
                return new DataFlowElement(outputColumn.Name, outputColumn);
            }
            else
            {
                return new DataFlowElement();
            }
        }

        /// <summary>
        /// Look for output column in the given output collection that is mapped using the given lineage ID.
        /// </summary>
        /// <param name="lineageID"></param>
        /// <param name="output"></param>
        /// <returns>data flow element that wraps output column</returns>
        private static DataFlowElement FindMappedOutputColumnElement(int targetLineageID, IDTSOutput100 output)
        {
            Debug.Assert(output != null);

            IDTSOutputColumnCollection100 outputColumnCollection = output.OutputColumnCollection;
            //int outputColumnCount = outputColumnCollection.Count;

            DataFlowElement retOutputElement = new DataFlowElement();

            foreach (IDTSOutputColumn100 outputColumn in outputColumnCollection)
            {
                IDTSCustomProperty100 property = GetCustomProperty(InputColumnLineageIdPropName, outputColumn.CustomPropertyCollection);
                if (property != null && property.Value is int)
                {
                    int lineageId = (int)property.Value;
                    if (lineageId == targetLineageID)
                    {
                        retOutputElement = new DataFlowElement(outputColumn.Name, outputColumn);
                        break;
                    }
                }
            }

            return retOutputElement;
        }

        #endregion
    }
}
